<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>3D窗帘可视化</title>
  <style>
    * {
      padding: 0;
      margin: 0;
    }

    body {
      margin: 0;
      overflow: hidden;
      font-family: "Arial", sans-serif;
      color: #333;
      /* 将文本颜色改为深色，适应白色背景 */
      display: flex;
      flex-direction: column;
      align-items: center;
      box-sizing: border-box;
      background: #ffffff;
      /* 背景改为白色 */
    }

    .container {
      display: flex;
      flex-direction: column;
      align-items: center;
      width: 100%;
      max-width: 600px;
      justify-content: center;
    }

    #scene-container {
      width: 100%;
      max-width: 600px;
      height: 400px;
      border: 2px solid rgba(0, 0, 0, 0.1);
      /* 边框颜色变浅，适应白色背景 */
      border-radius: 8px;
      box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
      /* 阴影变浅 */
    }

    @media (max-width: 600px) {
      #scene-container {
        height: 350px;
      }

      .container {
        justify-content: flex-start;
        padding-top: 20px;
      }
    }
  </style>

  <!-- 修复后的脚本加载顺序：核心库 → 扩展加载器 → 控制器 -->
  <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/RGBELoader.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/OBJLoader.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/MTLLoader.js"></script>

  <!-- 导入base64变量 -->
  <script src="./js/url.js"></script>
</head>

<body>
  <div class="container">
    <div id="scene-container"></div>
  </div>

  <script>
    const haUrl = "ws://172.20.80.1:8999/api/websocket";
    const accessToken =
      "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkNjA1OTFlZmQ5ZTg0OTQ2OWMxMTM0ZDJkYjEwYjJiNyIsImlhdCI6MTc1ODE4OTQyNywiZXhwIjoyMDczNTQ5NDI3fQ.27uaWBHzibkpFBfyv37NN1puqwHtRu5bPTuY8z1a0fI";
    const entityId = "cover.living_room_curtain";
    let model;

    let scene, camera, renderer, controls;
    let leftCurtainFront, rightCurtainFront;
    let leftCurtainBack, rightCurtainBack;
    let curtainGeometry;
    let socket = null;
    let messageId = 1;
    let subscriptionId = null;

    function initThreeJS() {
      // 创建场景
      scene = new THREE.Scene();
      scene.background = new THREE.Color(0xffffff); // 场景背景改为白色

      const container = document.getElementById("scene-container");
      const aspect = container.clientWidth / container.clientHeight;
      // 创建摄像机
      camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
      camera.position.set(0, 2, 5); // 调整相机位置以适应更大的窗帘

      // 创建渲染器
      renderer = new THREE.WebGLRenderer({ antialias: true });
      renderer.setSize(container.clientWidth, container.clientHeight);
      renderer.setPixelRatio(window.devicePixelRatio);
      container.appendChild(renderer.domElement);

      controls = new THREE.OrbitControls(camera, renderer.domElement);
      controls.enableDamping = true;
      controls.dampingFactor = 0.05;
      controls.minDistance = 1;
      controls.maxDistance = 15;
      controls.minAzimuthAngle = -Infinity;
      controls.maxAzimuthAngle = Infinity;
      controls.minPolarAngle = 0;
      controls.maxPolarAngle = Math.PI;
      controls.target.set(0, 2, 0);
      controls.update();

      renderer.domElement.addEventListener("dblclick", () => {
        camera.position.set(0, 2, 5);
        controls.target.set(0, 2, 0);
        controls.reset();
      });

      // 增强光照（在白色背景下可能需要调整光照强度）
      const ambientLight = new THREE.AmbientLight(0x808080, 0.9);
      scene.add(ambientLight);

      const frontLight = new THREE.DirectionalLight(0xffffff, 0.8);
      frontLight.position.set(3, 6, 4);
      frontLight.castShadow = true;
      scene.add(frontLight);

      const backLight = new THREE.DirectionalLight(0xffffff, 0.8);
      backLight.position.set(-3, 6, -4);
      backLight.castShadow = true;
      scene.add(backLight);
      // 创建窗帘
      createCurtain();

      window.addEventListener("resize", onWindowResize);

      const textureLoader = new THREE.TextureLoader();
      const base64Url = oneFMtlUrl;

      // 使用 atob 解码 base64
      const mtlLoader = new THREE.MTLLoader();
      const mtlString = atob(base64Url);
      const metals = mtlLoader.parse(mtlString);

      // 加载obj模型
      const loader = new THREE.OBJLoader();
      //加载原料
      loader.setMaterials(metals);
      const base64String = oneFapartmentUrl;
      // 将base64字符串转换为Blob对象
      const byteCharacters = atob(base64String);
      const byteArrays = [];
      for (let offset = 0; offset < byteCharacters.length; offset++) {
        const byteArray = new Uint8Array(1);
        byteArray[0] = byteCharacters.charCodeAt(offset);
        byteArrays.push(byteArray);
      }
      const byteArray = new Uint8Array(byteArrays.length);
      byteArrays.forEach((arr, i) => {
        byteArray[i] = arr[0];
      });
      // 创建Blob对象
      const blob = new Blob([byteArray], {
        type: "application/octet-stream",
      });
      const url = URL.createObjectURL(blob);
      loader.load(
        url,
        (object) => {
          scene.add(object);
          model = object;
          // 计算模型的包围盒
          const box = new THREE.Box3().setFromObject(model);
          console.log("模型的包围盒:", box);
          //创建包围盒辅助线
          const boxHelper = new THREE.Box3Helper(box, 0xff0000); // 红色
          scene.add(boxHelper);
          setTimeout(() => {
            adjustCameraToFitModel();
          }, 100);
          URL.revokeObjectURL(url); // 释放URL对象
        },
        (xhr) => {
          console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
        },
        (error) => {
          console.log("加载模型出错：", error);
        }
      );

      //  创建辅助线条
      const axesHelper = new THREE.AxesHelper(5);
      // // 添加到场景中
      scene.add(axesHelper);

      animate();
    }

    // 根据模型的尺寸来自动调整相机位置
    function adjustCameraToFitModel() {
      if (!model) return;

      const box = new THREE.Box3().setFromObject(model);
      const size = box.getSize(new THREE.Vector3()); // 获取模型的大小
      console.log("模型的大小:", size);

      const center = box.getCenter(new THREE.Vector3()); // 获取模型的中心点
      console.log("模型的中心点:", center);

      // 计算模型的最大边长和缩放比例
      const maxDimension = Math.max(size.x, size.y, size.z);

      const container = document.getElementById("scene-container");
      const viewSize = Math.min(container.clientWidth, container.clientHeight);
      console.log("viewSize", viewSize);

      console.log("模型的最大边长:", maxDimension);
      const scaleFactor = 30 / maxDimension; // 让模型的最大尺寸为5


      console.log("缩放比例:", scaleFactor);

      model.scale.set(scaleFactor, scaleFactor, scaleFactor); // 设置模型的缩放

      // 重新计算缩放后的包围盒子
      box.setFromObject(model);
      box.getSize(size);
      box.getCenter(center);

      // 计算相机的的位置 - 从上方向下看
      const distant = calculateCameraDistance(box, camera, container);
      console.log('center', center);

      // 更新相机的远近平面以适应模型的尺寸
      controls.target.set(center.x, 10, center.z);

      updateCameraNearFar(camera, box, distant);


      // 计算相机位置
      const distance = maxDimension * 2; // 相机距离模型的合适距离
      console.log("相机距离", distance);
      camera.position.set(center.x, 100, distance); //设置相机的位置
      controls.update(); // 更新控制器
    }


    // 根据模型的尺寸来自动调整相机位置 - 俯视视角
    function adjustCameraToFitModel() {
      if (!model) return;

      const box = new THREE.Box3().setFromObject(model);
      const size = box.getSize(new THREE.Vector3()); // 获取模型的大小
      const center = box.getCenter(new THREE.Vector3()); // 获取模型的中心点

      console.log("模型的大小:", size);
      console.log("模型的中心点:", center);

      // 计算模型的最大边长和缩放比例
      const maxDimension = Math.max(size.x, size.y, size.z);
      const container = document.getElementById("scene-container");

      console.log("模型的最大边长:", maxDimension);

      // 调整缩放比例，让模型更小以适应俯视视角
      const scaleFactor = 15 / maxDimension; // 减小分母让模型更小

      console.log("缩放比例:", scaleFactor);

      model.scale.set(scaleFactor, scaleFactor, scaleFactor); // 设置模型的缩放

      // 重新计算缩放后的包围盒
      box.setFromObject(model);
      box.getSize(size);
      box.getCenter(center);

      console.log("缩放后模型大小:", size);
      console.log("缩放后中心点:", center);

      // 设置俯视视角
      setupTopDownView(box, camera, container, center);

      controls.target.set(center.x, center.y, center.z);
      controls.update();
    }

    // 设置俯视视角的函数
    function setupTopDownView(box, camera, container, center) {
      const size = box.getSize(new THREE.Vector3());

      // 俯视视角：相机在模型正上方，向下看
      const maxDimension = Math.max(size.x, size.y, size.z);
      const aspect = container.clientWidth / container.clientHeight;

      // 计算合适的距离 - 确保模型完全可见
      let distance;
      if (aspect > 1) {
        // 宽屏：以高度为基准
        distance = size.y / (2 * Math.tan(camera.fov * Math.PI / 360));
      } else {
        // 高屏：以宽度为基准
        distance = size.x / (2 * Math.tan(camera.fov * Math.PI / 360) * aspect);
      }

      // 增加安全距离，确保模型完全在视野内
      distance *= 8;

      console.log("计算的距离:", distance);

      // 设置相机在模型正上方（俯视视角）
      camera.position.set(center.x, center.y + distance, center.z);

      // 相机向下看（真正的俯视）
      camera.lookAt(center.x, center.y, center.z);

      // 设置合适的近远平面
      camera.near = Math.max(0.1, distance * 0.1);
      camera.far = distance * 5;
      camera.updateProjectionMatrix();

      console.log("相机位置:", camera.position);
      console.log("相机看向点:", center);
    }

    // 计算相机距离的函数
    function calculateCameraDistance(box, camera, container) {
      const size = box.getSize(new THREE.Vector3());
      const center = box.getCenter(new THREE.Vector3());

      // 计算模型的最大尺寸
      const modelSize = Math.max(size.x, size.y, size.z);


      // 计算视场角和容器的宽高比
      const fov = camera.fov * (Math.PI / 180); // 转换为弧度
      const aspect = container.clientWidth / container.clientHeight;
      let distance;

      // 计算保证模型完全可见的距离

      if (aspect > 1) {
        // 宽屏
        distance = modelSize / (2 * Math.tan(fov / 2));
      } else {
        // 高屏
        distance = modelSize / (2 * Math.tan(fov / 2) / aspect);
      }

      // 增加一些余量
      return distance * 1.5;
    }

    // 更新相机近远平面
    function updateCameraNearFar(camera, box, distance) {
      const size = box.getSize(new THREE.Vector3());
      const maxDimension = Math.max(size.x, size.y, size.z);

      camera.near = Math.max(0.1, distance - maxDimension * 2);
      camera.far = distance + maxDimension * 2;
      camera.updateProjectionMatrix();
    }


    function createCurtain() {
      curtainGeometry = new THREE.PlaneGeometry(1.1, 0.75, 20, 20);

      // 米黄色材质（正面）
      const frontMaterial = new THREE.MeshStandardMaterial({
        color: 0xf5deb3, // 米黄色
        roughness: 0.9,
        metalness: 0.1,
        side: THREE.FrontSide,
      });

      // 背面材质（稍浅的米黄色）
      const backMaterial = new THREE.MeshStandardMaterial({
        color: 0xf8ecc9,
        roughness: 0.9,
        metalness: 0.1,
        side: THREE.BackSide,
      });

      // 左侧窗帘（正面+背面）
      leftCurtainFront = new THREE.Mesh(curtainGeometry, frontMaterial);
      leftCurtainBack = new THREE.Mesh(curtainGeometry, backMaterial);
      leftCurtainFront.position.set(2.2, 0.4, 2.4); // 调整位置以适应更大的窗帘
      leftCurtainBack.position.set(2.2, 0.4, 2.4);
      leftCurtainFront.castShadow = true;
      leftCurtainBack.castShadow = true;
      scene.add(leftCurtainFront);
      scene.add(leftCurtainBack);

      // 右侧窗帘（正面+背面）
      rightCurtainFront = new THREE.Mesh(curtainGeometry, frontMaterial);
      rightCurtainBack = new THREE.Mesh(curtainGeometry, backMaterial);
      rightCurtainFront.position.set(3.3, 0.4, 2.4); // 调整位置以适应更大的窗帘
      rightCurtainBack.position.set(3.3, 0.4, 2.4);
      rightCurtainFront.castShadow = true;
      rightCurtainBack.castShadow = true;
      scene.add(rightCurtainFront);
      scene.add(rightCurtainBack);

      // 添加更长的窗帘杆以匹配更大的窗帘
      const rodGeometry = new THREE.CylinderGeometry(0.02, 0.02, 2.3, 16); // 加长窗帘杆
      const rodMaterial = new THREE.MeshStandardMaterial({ color: 0x888888 });
      const rod = new THREE.Mesh(rodGeometry, rodMaterial);
      rod.position.set(2.75, 0.7, 2.4); // 调整位置
      rod.rotation.z = Math.PI / 2;
      scene.add(rod);

      // 添加窗帘环
      // const ringGeometry = new THREE.TorusGeometry(0.03, 0.01, 16, 16);
      // const ringMaterial = new THREE.MeshStandardMaterial({
      //   color: 0xcccccc,
      // });
      // 调整后的代码
      const ringGeometry = new THREE.TorusGeometry(0.03, 0.01, 16, 16);
      const ringMaterial = new THREE.MeshStandardMaterial({
        color: 0xcccccc,
      });

      // 左侧窗帘环（增加数量以匹配更长的窗帘）
      // for (let i = 0; i < 8; i++) {
      //   const ring = new THREE.Mesh(ringGeometry, ringMaterial);
      //   ring.position.set(-2.2 + i * 0.35, 4.2, 0);
      //   scene.add(ring);
      // }
      for (let i = 0; i < 7; i++) {
        const ring = new THREE.Mesh(ringGeometry, ringMaterial);
        ring.position.set(1.7 + i * 0.15, 0.7, 2.4); // X: 2.2→3.3（覆盖左侧窗帘），Y/Z与杆一致
        scene.add(ring);
      }

      // 右侧窗帘环（增加数量以匹配更长的窗帘）
      // for (let i = 0; i < 7; i++) {
      //   const ring = new THREE.Mesh(ringGeometry, ringMaterial);
      //   ring.position.set(0.3 + i * 0.35, 4.2, 0);
      //   scene.add(ring);
      // }

      for (let i = 0; i < 8; i++) {
        const ring = new THREE.Mesh(ringGeometry, ringMaterial);
        ring.position.set(2.7 + i * 0.15, 0.7, 2.4); // X: 3.3→4.35（覆盖右侧窗帘），Y/Z与杆一致
        scene.add(ring);
      }
    }

    function updateCurtain(position) {
      const openAmount = position / 100;

      // 获取窗帘的初始位置（从createCurtain函数中的设置）
      const leftInitialX = 2.2; // 左侧窗帘初始X位置
      const rightInitialX = 3.3; // 右侧窗帘初始X位置

      // 移动窗帘（调整移动距离以适应更大的窗帘）
      const moveDistance = 1.1 * openAmount;
      // 移动窗帘
      leftCurtainFront.position.x = leftInitialX - moveDistance;
      leftCurtainBack.position.x = leftInitialX - moveDistance;
      rightCurtainFront.position.x = rightInitialX + moveDistance;
      rightCurtainBack.position.x = rightInitialX + moveDistance;

      // 应用折叠效果（打开时）
      if (openAmount > 0) {
        applyFoldingEffect(openAmount);
      } else {
        // 关闭时恢复平整
        resetFoldingEffect();
      }

      // 更新材质透明度
      const opacity = 1 - openAmount * 0.5;
      // leftCurtainFront.material.opacity = opacity;
      // rightCurtainFront.material.opacity = opacity;
      // leftCurtainBack.material.opacity = opacity;
      // rightCurtainBack.material.opacity = opacity;

      // leftCurtainFront.material.transparent = opacity < 1;
      // rightCurtainFront.material.transparent = opacity < 1;
      // leftCurtainBack.material.transparent = opacity < 1;
      // rightCurtainBack.material.transparent = opacity < 1;
    }

    function applyFoldingEffect(openAmount) {
      const positionAttribute = curtainGeometry.getAttribute("position");
      const vertexCount = positionAttribute.count;
      const positions = positionAttribute.array;

      // 保存原始位置（如果尚未保存）
      if (!curtainGeometry.userData.originalPositions) {
        curtainGeometry.userData.originalPositions = new Float32Array(
          positions
        );
      }

      // 应用折叠效果
      for (let i = 0; i < vertexCount; i++) {
        const x = curtainGeometry.userData.originalPositions[i * 3];
        const y = curtainGeometry.userData.originalPositions[i * 3 + 1];



        // 根据位置和打开程度计算折叠
        const foldIntensity = openAmount * 0.1;
        const foldX = Math.sin(x * 10 + openAmount * 5) * foldIntensity;
        const foldY = Math.cos(y * 5) * foldIntensity * 0.5;

        positions[i * 3 + 2] = foldX + foldY;
      }

      positionAttribute.needsUpdate = true;

      // 更新法线以实现正确光照
      curtainGeometry.computeVertexNormals();
    }

    function resetFoldingEffect() {
      if (curtainGeometry.userData.originalPositions) {
        const positionAttribute = curtainGeometry.getAttribute("position");
        const originalPositions = curtainGeometry.userData.originalPositions;

        for (let i = 0; i < originalPositions.length; i++) {
          positionAttribute.array[i] = originalPositions[i];
        }

        positionAttribute.needsUpdate = true;
        curtainGeometry.computeVertexNormals();
      }
    }

    function onWindowResize() {
      const container = document.getElementById("scene-container");
      camera.aspect = container.clientWidth / container.clientHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(container.clientWidth, container.clientHeight);
    }

    function animate() {
      requestAnimationFrame(animate);
      controls.update();
      renderer.render(scene, camera);
    }

    // WebSocket相关逻辑
    function connectWebSocket() {
      socket = new WebSocket(haUrl);
      socket.onopen = (e) => {
        console.log("WebSocket连接已打开:", e);
        // 授权
        authenticate();
      };
      socket.onmessage = (e) => {
        console.log("WebSocket收到消息:", e);
        handleWebSocketMessage(JSON.parse(e.data));
      };
      socket.onclose = () => setTimeout(connectWebSocket, 5000);
      socket.onerror = () => { };
    }

    function handleWebSocketMessage(message) {
      switch (message.type) {
        case "auth_required":
          authenticate();
          break;
        case "auth_ok":
          subscribeToEvents();
          break;
        case "result":
          if (message.success && message.id === 2) {
            subscriptionId = message.result.id;
            getCurrentState();
          }
          break;
        case "event":
          if (message.event?.event_type === "state_changed") {
            const newState = message.event.data.new_state;
            if (newState?.entity_id === entityId) updateUI(newState);
          }
          break;
      }
    }

    function authenticate() {
      socket.send(
        JSON.stringify({ type: "auth", access_token: accessToken })
      );
    }

    function subscribeToEvents() {
      socket.send(
        JSON.stringify({
          id: messageId++,
          type: "subscribe_events",
          event_type: "state_changed",
        })
      );
    }

    function getCurrentState() {
      const id = messageId++;
      socket.send(JSON.stringify({ id, type: "get_states" }));
      const originalOnMessage = socket.onmessage;
      socket.onmessage = (e) => {
        const msg = JSON.parse(e.data);
        if (msg.type === "result" && msg.id === id) {
          if (msg.success) {
            const curtain = msg.result.find((s) => s.entity_id === entityId);
            if (curtain) updateUI(curtain);
          }
          socket.onmessage = originalOnMessage;
        } else originalOnMessage(e);
      };
    }

    function updateUI(state) {
      const position =
        state.attributes.current_position ??
        (state.state === "open" ? 100 : 0);
      updateCurtain(position);
    }

    window.onload = () => {
      initThreeJS();
      connectWebSocket();
      // updateCurtain(50);
    };
  </script>
</body>

</html>